/*
 This file is part of Subsonic.

 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 Subsonic is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.

 Copyright 2009 (C) Sindre Mehus
 */
package net.nullsum.audinaut.domain;

import android.content.Context;
import android.media.MediaMetadataRetriever;
import android.util.Log;

import net.nullsum.audinaut.service.DownloadService;
import net.nullsum.audinaut.util.Constants;
import net.nullsum.audinaut.util.UpdateHelper;
import net.nullsum.audinaut.util.Util;

import java.io.File;
import java.io.Serializable;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

/**
 * @author Sindre Mehus
 */
public class MusicDirectory implements Serializable {
    private static final String TAG = MusicDirectory.class.getSimpleName();

    private String name;
    private String id;
    private String parent;
    private List<Entry> children;

    public MusicDirectory() {
        children = new ArrayList<>();
    }

    public MusicDirectory(List<Entry> children) {
        this.children = children;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getParent() {
        return parent;
    }

    public void setParent(String parent) {
        this.parent = parent;
    }

    public void addChild(Entry child) {
        if (child != null) {
            children.add(child);
        }
    }

    public void addChildren(List<Entry> children) {
        this.children.addAll(children);
    }

    public void replaceChildren(List<Entry> children) {
        this.children = children;
    }

    public synchronized List<Entry> getChildren() {
        return getChildren(true, true);
    }

    public synchronized List<Entry> getChildren(boolean includeDirs, boolean includeFiles) {
        if (includeDirs && includeFiles) {
            return children;
        }

        List<Entry> result = new ArrayList<>(children.size());
        for (Entry child : children) {
            if (child != null && child.isDirectory() && includeDirs || !child.isDirectory() && includeFiles) {
                result.add(child);
            }
        }
        return result;
    }

    public synchronized List<Entry> getSongs() {
        List<Entry> result = new ArrayList<>();
        for (Entry child : children) {
            if (child != null && !child.isDirectory()) {
                result.add(child);
            }
        }
        return result;
    }

    public synchronized int getChildrenSize() {
        return children.size();
    }

    public void sortChildren(Context context) {
        sortChildren(Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_CUSTOM_SORT_ENABLED, true));
    }

    public void sortChildren(boolean byYear) {
        EntryComparator.sort(children, byYear);
    }

    public synchronized boolean updateMetadata(MusicDirectory refreshedDirectory) {
        boolean metadataUpdated = false;
        for (Entry entry : children) {
            int index = refreshedDirectory.children.indexOf(entry);
            if (index != -1) {
                final Entry refreshed = refreshedDirectory.children.get(index);

                entry.setTitle(refreshed.getTitle());
                entry.setAlbum(refreshed.getAlbum());
                entry.setArtist(refreshed.getArtist());
                entry.setTrack(refreshed.getTrack());
                entry.setYear(refreshed.getYear());
                entry.setGenre(refreshed.getGenre());
                entry.setTranscodedContentType(refreshed.getTranscodedContentType());
                entry.setTranscodedSuffix(refreshed.getTranscodedSuffix());
                entry.setDiscNumber(refreshed.getDiscNumber());
                entry.setType(refreshed.getType());
                if (!Util.equals(entry.getCoverArt(), refreshed.getCoverArt())) {
                    metadataUpdated = true;
                    entry.setCoverArt(refreshed.getCoverArt());
                }

                new UpdateHelper.EntryInstanceUpdater(entry) {
                    @Override
                    public void update(Entry found) {
                        found.setTitle(refreshed.getTitle());
                        found.setAlbum(refreshed.getAlbum());
                        found.setArtist(refreshed.getArtist());
                        found.setTrack(refreshed.getTrack());
                        found.setYear(refreshed.getYear());
                        found.setGenre(refreshed.getGenre());
                        found.setTranscodedContentType(refreshed.getTranscodedContentType());
                        found.setTranscodedSuffix(refreshed.getTranscodedSuffix());
                        found.setDiscNumber(refreshed.getDiscNumber());
                        found.setType(refreshed.getType());
                        if (!Util.equals(found.getCoverArt(), refreshed.getCoverArt())) {
                            found.setCoverArt(refreshed.getCoverArt());
                            metadataUpdate = DownloadService.METADATA_UPDATED_COVER_ART;
                        }
                    }
                }.execute();
            }
        }

        return metadataUpdated;
    }

    public synchronized boolean updateEntriesList(Context context, MusicDirectory refreshedDirectory) {
        boolean changed = false;
        Iterator<Entry> it = children.iterator();
        while (it.hasNext()) {
            Entry entry = it.next();
            // No longer exists in here
            if (refreshedDirectory.children.indexOf(entry) == -1) {
                it.remove();
                changed = true;
            }
        }

        // Make sure we contain all children from refreshed set
        boolean resort = false;
        for (Entry refreshed : refreshedDirectory.children) {
            if (!this.children.contains(refreshed)) {
                this.children.add(refreshed);
                resort = true;
                changed = true;
            }
        }

        if (resort) {
            this.sortChildren(context);
        }

        return changed;
    }

    public static class Entry implements Serializable {
        public static final int TYPE_SONG = 0;

        private String id;
        private String parent;
        private String albumId;
        private String artistId;
        private boolean directory;
        private String title;
        private String album;
        private String artist;
        private Integer track;
        private Integer year;
        private String genre;
        private String contentType;
        private String suffix;
        private String transcodedContentType;
        private String transcodedSuffix;
        private String coverArt;
        private Integer duration;
        private Integer bitRate;
        private String path;
        private Integer discNumber;
        private int type = 0;
        private int closeness;

        public Entry() {

        }

        public Entry(String id) {
            this.id = id;
        }

        public Entry(Artist artist) {
            this.id = artist.getId();
            this.title = artist.getName();
            this.directory = true;
        }

        public void loadMetadata(File file) {
            try {
                MediaMetadataRetriever metadata = new MediaMetadataRetriever();
                metadata.setDataSource(file.getAbsolutePath());
                String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER);
                if (discNumber == null) {
                    discNumber = "1/1";
                }
                int slashIndex = discNumber.indexOf("/");
                if (slashIndex > 0) {
                    discNumber = discNumber.substring(0, slashIndex);
                }
                try {
                    setDiscNumber(Integer.parseInt(discNumber));
                } catch (Exception e) {
                    Log.w(TAG, "Non numbers in disc field!");
                }
                String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE);
                setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000);
                String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                setDuration(Integer.parseInt(length) / 1000);
                String artist = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
                if (artist != null) {
                    setArtist(artist);
                }
                String album = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
                if (album != null) {
                    setAlbum(album);
                }
                metadata.release();
            } catch (Exception e) {
                Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver", e);
            }
        }

        public void rebaseTitleOffPath() {
            try {
                String filename = getPath();
                if (filename == null) {
                    return;
                }

                int index = filename.lastIndexOf('/');
                if (index != -1) {
                    filename = filename.substring(index + 1);
                    if (getTrack() != null) {
                        filename = filename.replace(String.format("%02d ", getTrack()), "");
                    }

                    index = filename.lastIndexOf('.');
                    if (index != -1) {
                        filename = filename.substring(0, index);
                    }

                    setTitle(filename);
                }
            } catch (Exception e) {
                Log.w(TAG, "Failed to update title based off of path", e);
            }
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getParent() {
            return parent;
        }

        public void setParent(String parent) {
            this.parent = parent;
        }

        public String getAlbumId() {
            return albumId;
        }

        public void setAlbumId(String albumId) {
            this.albumId = albumId;
        }

        public String getArtistId() {
            return artistId;
        }

        public void setArtistId(String artistId) {
            this.artistId = artistId;
        }

        public boolean isDirectory() {
            return directory;
        }

        public void setDirectory(boolean directory) {
            this.directory = directory;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getAlbum() {
            return album;
        }

        public boolean isAlbum() {
            return getParent() != null || getArtist() != null;
        }

        public void setAlbum(String album) {
            this.album = album;
        }

        public String getAlbumDisplay() {
            if (album != null && title.startsWith("Disc ")) {
                return album;
            } else {
                return title;
            }
        }

        public String getArtist() {
            return artist;
        }

        public void setArtist(String artist) {
            this.artist = artist;
        }

        public Integer getTrack() {
            return track;
        }

        public void setTrack(Integer track) {
            this.track = track;
        }

        public Integer getYear() {
            return year;
        }

        public void setYear(Integer year) {
            this.year = year;
        }

        public String getGenre() {
            return genre;
        }

        public void setGenre(String genre) {
            this.genre = genre;
        }

        public String getContentType() {
            return contentType;
        }

        public void setContentType(String contentType) {
            this.contentType = contentType;
        }

        public String getSuffix() {
            return suffix;
        }

        public void setSuffix(String suffix) {
            this.suffix = suffix;
        }

        public String getTranscodedContentType() {
            return transcodedContentType;
        }

        public void setTranscodedContentType(String transcodedContentType) {
            this.transcodedContentType = transcodedContentType;
        }

        public String getTranscodedSuffix() {
            return transcodedSuffix;
        }

        public void setTranscodedSuffix(String transcodedSuffix) {
            this.transcodedSuffix = transcodedSuffix;
        }

        public Integer getDuration() {
            return duration;
        }

        public void setDuration(Integer duration) {
            this.duration = duration;
        }

        public Integer getBitRate() {
            return bitRate;
        }

        public void setBitRate(Integer bitRate) {
            this.bitRate = bitRate;
        }

        public String getCoverArt() {
            return coverArt;
        }

        public void setCoverArt(String coverArt) {
            this.coverArt = coverArt;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public Integer getDiscNumber() {
            return discNumber;
        }

        public void setDiscNumber(Integer discNumber) {
            this.discNumber = discNumber;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

        public boolean isSong() {
            return type == TYPE_SONG;
        }

        public int getCloseness() {
            return closeness;
        }

        public void setCloseness(int closeness) {
            this.closeness = closeness;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            Entry entry = (Entry) o;
            return id.equals(entry.id);
        }

        @Override
        public int hashCode() {
            return id.hashCode();
        }

        @Override
        public String toString() {
            return title;
        }
    }

    public static class EntryComparator implements Comparator<Entry> {
        private final boolean byYear;
        private final Collator collator;

        public EntryComparator(boolean byYear) {
            this.byYear = byYear;
            this.collator = Collator.getInstance(Locale.US);
            this.collator.setStrength(Collator.PRIMARY);
        }

        public static void sort(List<Entry> entries, boolean byYear) {
            try {
                Collections.sort(entries, new EntryComparator(byYear));
            } catch (Exception e) {
                Log.w(TAG, "Failed to sort MusicDirectory");
            }
        }

        public int compare(Entry lhs, Entry rhs) {
            if (lhs.isDirectory() && !rhs.isDirectory()) {
                return -1;
            } else if (!lhs.isDirectory() && rhs.isDirectory()) {
                return 1;
            } else if (lhs.isDirectory() && rhs.isDirectory()) {
                if (byYear) {
                    Integer lhsYear = lhs.getYear();
                    Integer rhsYear = rhs.getYear();
                    if (lhsYear != null && rhsYear != null) {
                        return lhsYear.compareTo(rhsYear);
                    } else if (lhsYear != null) {
                        return -1;
                    } else if (rhsYear != null) {
                        return 1;
                    }
                }

                return collator.compare(lhs.getAlbumDisplay(), rhs.getAlbumDisplay());
            }

            Integer lhsDisc = lhs.getDiscNumber();
            Integer rhsDisc = rhs.getDiscNumber();

            if (lhsDisc != null && rhsDisc != null) {
                if (lhsDisc < rhsDisc) {
                    return -1;
                } else if (lhsDisc > rhsDisc) {
                    return 1;
                }
            }

            Integer lhsTrack = lhs.getTrack();
            Integer rhsTrack = rhs.getTrack();
            if (lhsTrack != null && rhsTrack != null && !Objects.equals(lhsTrack, rhsTrack)) {
                return lhsTrack.compareTo(rhsTrack);
            } else if (lhsTrack != null) {
                return -1;
            } else if (rhsTrack != null) {
                return 1;
            }

            return collator.compare(lhs.getTitle(), rhs.getTitle());
        }
    }
}
